/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Cfg, BasicBlock, ArkMethod, Constant, ClassType, Value, Stmt } from 'arkanalyzer/lib';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Rule, Defects, MethodMatcher, MatcherTypes, MatcherCallback } from '../../Index';
import { IssueReport } from '../../model/Defects';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoRegexInLoopCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: 'docs/no-regex-in-loop.md',
    description: 'Placing the creation of regular expressions outside the loop.'
};

interface WarnInfo {
    lineNum: number;
    startCol: number;
    endCol: number;
    filePath: string;
};

class CfgLoopAnalyzer {
    private index = 0;
    private stack: number[] = []; // 使用 ID 存储栈
    private indexMap = new Map<number, number>(); // ID → index
    private lowLinkMap = new Map<number, number>(); // ID → lowLink
    private onStackSet = new Set<number>(); // 使用 Set 优化栈检查
    private loopBlocks = new Set<number>(); // 存储循环块的 ID
    private blockMap = new Map<number, BasicBlock>(); // ID → BasicBlock

    constructor(private cfg: Cfg) {
        // 初始化 blockMap
        for (const block of cfg.getBlocks()) {
            this.blockMap.set(block.getId(), block);
        }
    }

    /** 获取所有位于循环中的基本块 */
    public getLoopBlocks(): Set<BasicBlock> {
        this.resetState();
        this.findSCCs();
        
        // 将 ID 集合转换为 BasicBlock 集合
        const result = new Set<BasicBlock>();
        for (const blockId of this.loopBlocks) {
            const block = this.blockMap.get(blockId);
            if (block) {
                result.add(block);
            }
        }
        return result;
    }

    /** 使用 Tarjan 算法找出所有强连通分量并识别循环块 */
    private findSCCs(): void {
        for (const block of this.cfg.getBlocks()) {
            const blockId = block.getId();
            if (!this.indexMap.has(blockId)) {
                this.strongConnect(block);
            }
        }
    }

    private strongConnect(block: BasicBlock): void {
        const blockId = block.getId();
        
        // 设置深度索引并压入栈中
        this.indexMap.set(blockId, this.index);
        this.lowLinkMap.set(blockId, this.index);
        this.index++;
        this.stack.push(blockId);
        this.onStackSet.add(blockId);

        // 处理所有后继节点
        for (const successor of block.getSuccessors()) {
            const succId = successor.getId();
            
            if (!this.indexMap.has(succId)) {
                // 后继节点未被访问，递归处理
                this.strongConnect(successor);
                this.lowLinkMap.set(
                    blockId,
                    Math.min(
                        this.lowLinkMap.get(blockId)!,
                        this.lowLinkMap.get(succId)!
                    )
                );
            } else if (this.onStackSet.has(succId)) {
                // 后继节点已在栈中，更新 lowLink
                this.lowLinkMap.set(
                    blockId,
                    Math.min(
                        this.lowLinkMap.get(blockId)!,
                        this.indexMap.get(succId)!
                    )
                );
            }
        }

        // 如果这是一个根节点，弹出 SCC
        if (this.lowLinkMap.get(blockId)! === this.indexMap.get(blockId)!) {
            const scc: number[] = [];
            let nextId: number;
            
            do {
                nextId = this.stack.pop()!;
                this.onStackSet.delete(nextId);
                scc.push(nextId);
            } while (nextId !== blockId);

            // 如果 SCC 大小大于 1，则认为是循环
            if (scc.length > 1) {
                scc.forEach(id => this.loopBlocks.add(id));
            }
        }
    }

    /** 重置分析状态 */
    private resetState(): void {
        this.index = 0;
        this.stack = [];
        this.indexMap.clear();
        this.lowLinkMap.clear();
        this.onStackSet.clear();
        this.loopBlocks.clear();
    }
}

function getLoopBlocksInCFG(cfg: Cfg): Set<BasicBlock> {
    const analyzer = new CfgLoopAnalyzer(cfg);
    return analyzer.getLoopBlocks();
}

export class NoRegexInLoopCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    
    private mtdMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
    };

    public registerMatchers(): MatcherCallback[] {
        const matchMethodCb: MatcherCallback = {
            matcher: this.mtdMatcher,
            callback: this.check
        };
        return [matchMethodCb];
    }

    public check = (target: ArkMethod): void => {
        const cfg = target.getCfg();
        if (!cfg) {
            return;
        }
        const blocksInLoop = getLoopBlocksInCFG(cfg);
        for (const block of blocksInLoop) {
            for (const stmt of block.getStmts()) {
                stmt.getUses().forEach(u => this.checkUse(u, stmt, target));
            }
        }
    };

    private checkUse(use: Value, stmt: Stmt, method: ArkMethod): void {
        if (!(use instanceof Constant)) {
            return;
        }
        const type = use.getType();
        if (!(type instanceof ClassType) || type.getClassSignature().getClassName() !== 'RegExp') {
            return;
        }
        const pos = stmt.getOperandOriginalPosition(use);
        if (!pos) {
            logger.error(`cannot find position of regex in stmt: ${stmt.toString()}`);
            return;
        }
        const lineNum = pos.getFirstLine();
        const startCol = pos.getFirstCol();
        const endCol = startCol;
        const filePath = method.getDeclaringArkFile().getFilePath();
        this.addIssueReport({ lineNum, startCol, endCol, filePath });
    }
    
    private addIssueReport(warnInfo: WarnInfo): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        if (warnInfo.lineNum !== -1) {
            let defects = new Defects(
            warnInfo.lineNum,
            warnInfo.startCol,
            warnInfo.endCol,
            this.metaData.description,
            severity,
            this.rule.ruleId,
            warnInfo.filePath,
            this.metaData.ruleDocPath,
            true,
            false,
            false
            );
            this.issues.push(new IssueReport(defects, undefined));
        }
    }
}